#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <string.h>
#include "verilated.h"
#include "svdpi.h"
#include "macro.h"

#include "verilated_vcd_c.h"

#include "VExecute.h"
#include "VExecute__Dpi.h"
#include "VExecute___024root.h"
#include "VExecute_arithmetic_unit.h"
#include "VExecute_branch_unit.h"
#include "VExecute_load_unit.h"
#include "VExecute_logic_unit.h"
#include "VExecute_store_unit.h"

#define CONFIG_MSIZE 0x8000000
#define CONFIG_MBASE 0x80000000

extern "C" {
    bool fence_valid = false,ecall_valid = false,ebreak_valid = false;
    // DPI IMPORTS
    // DPI import at Decode/Decode.v:10:30
    void other_inst(svBit ecall, svBit fence, svBit ebreak) {
        if(ecall) ecall_valid = true;
        if(fence) fence_valid = true;
        if(ebreak) ebreak_valid = true;
    }
 
} 

int rand_n(bool sign,int n) {
  int res = rand()%n;
  if(!sign && res < 0) res = -res;
  return res; 
}

uint32_t sim_time = 0;

enum ALU {
    ARITHMETIC_UNIT = 0,
    BRANCH_UNIT,
    LOAD_UNIT,
    LOGIC_UNIT,
    STORE_UNIT,
    OTHER_UNIT,
    __ALU_NUM
};
void rand_op(VExecute *ex,enum ALU unit) {
    ex->enable = 1;
    ex->flush = 0;
    ex->ex_intr = 0;
    ex->tim_int_req = 0;
    ex->arithmetic_op = 0;
    ex->branch_op = 0;
    ex->load_op = 0;
    ex->logic_op = 0;
    ex->store_op = 0;
    ex->other_op = 0;
    switch (unit)
    {
    case ARITHMETIC_UNIT: ex->arithmetic_op = 1 << rand_n(false,6); break;
    case BRANCH_UNIT: ex->branch_op = 1 << rand_n(false,7); break;
    case LOAD_UNIT: ex->load_op = 1 << rand_n(false,7); break;
    case LOGIC_UNIT: ex->logic_op = 1 << rand_n(false,21); break;
    case STORE_UNIT: ex->store_op = 1 << rand_n(false,3); break;
    // case OTHER_UNIT: ex->other_op = 1 << rand_n(false,1); break;
    default: assert(0);break;
    }
}
char *inst_name = "null"; 
bool judgeOut(VExecute *ex) {
    uint64_t src1 = ex->src1;
    uint64_t src2 = ex->src2;
    uint64_t imm = ex->imm;
    uint64_t dest_data = ex->dest_data;
    uint64_t res = ~dest_data;
    uint64_t pc = ex->pc_i;
    inst_name = "null";
    bool pc_en = false;
    bool res_en = true;
    bool branch = false;
    bool load_en = false;
    bool store_en = false;
    uint8_t load_sign;
    uint64_t load_addr;
    uint64_t store_addr;
    uint64_t store_data;
    uint8_t store_bytes;
    uint64_t next_pc = 0;
    int op_num = 0;
    bool resw = false;
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_addw) { op_num++; inst_name = "addw"; res = SEXT(src1,32) + SEXT(src2,32); resw = true;}
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_addiw) { op_num++; inst_name = "addiw"; res = SEXT(src1,32) + SEXT(imm,32); resw = true;} 
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_subw) { op_num++; inst_name = "subw"; res = SEXT(src1,32) - SEXT(src2,32); resw = true;} 
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_add) { op_num++; inst_name = "add"; res = src1 + src2;} 
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_addi) { op_num++; inst_name = "addi"; res = src1 + imm;} 
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_sub) { op_num++; inst_name = "sub"; res = src1 - src2;} 
    if(ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_auipc) { op_num++; inst_name = "auipc"; res = pc + imm;} 
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_beq) { op_num++; pc_en = true; res_en = false; inst_name = "op_beq"; next_pc = src1==src2 ? pc+imm : pc+4;  branch = src1==src2 ; }
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_bge) { op_num++; pc_en = true; res_en = false; inst_name = "op_bge"; next_pc = (int64_t)src1>=(int64_t)src2?pc+imm:pc+4;  branch = (int64_t)src1>=(int64_t)src2; }
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_bgeu) { op_num++; pc_en = true; res_en = false; inst_name = "op_bgeu";  next_pc = src1 >= src2 ? pc+imm : pc+4; branch = src1>=src2 ;}
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_blt) { op_num++; pc_en = true; res_en = false; inst_name = "op_blt";  next_pc = (int64_t)src1<(int64_t)src2?pc+imm:pc+4;  branch = (int64_t)src1<(int64_t)src2; }
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_bltu) { op_num++; pc_en = true; res_en = false; inst_name = "op_bltu";  next_pc = src1 < src2 ? pc+imm : pc+4; branch = src1<src2;}
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_bne) { op_num++; pc_en = true; res_en = false; inst_name = "op_bne";  next_pc = src1!=src2 ? pc+imm : pc+4;  branch = src1!=src2; }
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_jal) { op_num++; pc_en = true; inst_name = "op_jal"; res = pc+4; next_pc = pc + imm; branch = true; }
    if(ex->__PVT__Execute__DOT__inst_branch_unit->op_jalr) { op_num++; pc_en = true; inst_name = "op_jalr"; res = pc+4; next_pc = imm + src1; branch = true; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_ld) { op_num++; load_en = true; res_en = false; inst_name = "ld"; load_addr = src1 + imm; load_sign = 0; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lw) { op_num++; load_en = true; res_en = false; inst_name = "lw"; load_addr = src1 + imm; load_sign = 1; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lwu) { op_num++; load_en = true; res_en = false; inst_name = "lwu"; load_addr = src1 + imm; load_sign = 0; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lh) { op_num++; load_en = true; res_en = false; inst_name = "lh"; load_addr = src1 + imm; load_sign = 1; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lhu) { op_num++; load_en = true; res_en = false; inst_name = "lhu"; load_addr = src1 + imm; load_sign = 0; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lb) { op_num++; load_en = true; res_en = false; inst_name = "lb"; load_addr = src1 + imm; load_sign = 1; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lbu) { op_num++; load_en = true; res_en = false; inst_name = "lbu"; load_addr = src1 + imm; load_sign = 0; }
    if(ex->__PVT__Execute__DOT__inst_load_unit->op_lui) { op_num++;  inst_name = "lui"; res = imm;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_slli) { op_num++; inst_name = "slli"; res = src1 << BITS(imm,5,0); }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_slliw) { op_num++; inst_name = "slliw"; res = SEXT(src1 << BITS(imm,4,0),32);  resw = true;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sllw) { op_num++; inst_name = "sllw"; res = SEXT(src1 << BITS(src2,4,0),32);  resw = true;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_srai) { op_num++; inst_name = "srai"; res = (int64_t)src1 >> BITS(imm,5,0); }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sraiw) { op_num++; inst_name = "sraiw"; res = SEXT((int64_t)SEXT(src1,32) >> BITS(imm,4,0),32);  resw = true;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sraw) { op_num++; inst_name = "sraw"; res =  SEXT((int64_t)SEXT(src1,32) >> BITS(src2,4,0),32);  resw = true;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_srli) { op_num++; inst_name = "srli"; res = src1 >> BITS(imm,5,0); }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_srliw) { op_num++; inst_name = "srliw"; res = SEXT((int64_t)SEXT(src1,32) >> BITS(imm,4,0),32);  resw = true;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_srlw) { op_num++; inst_name = "srlw"; res = SEXT((int64_t)SEXT(src1,32) >> BITS(src2,4,0),32);  resw = true;}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_and) { op_num++; inst_name = "and"; res = src1&src2; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_andi) { op_num++; inst_name = "andi"; res = src1&imm; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_or) { op_num++; inst_name = "or"; res = src1|src2; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_ori) { op_num++; inst_name = "ori"; res = src1|imm; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sll) { op_num++; inst_name = "sll"; res = src1 << BITS(src2,5,0);}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_slt) { op_num++; inst_name = "slt"; res = (int64_t)src1 < (int64_t)src2; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_slti) { op_num++; inst_name = "slti"; res = (int64_t)src1 < (int64_t)imm; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sltiu) { op_num++; inst_name = "sltiu"; res = src1 < imm; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sltu) { op_num++; inst_name = "sltu"; res = src1 < src2; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_sra) { op_num++; inst_name = "sra"; res = (int64_t)src1 >> BITS(src2,5,0);}
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_srl) { op_num++; inst_name = "srl"; res = src1 >> BITS(src2,5,0); }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_xor) { op_num++; inst_name = "xor"; res = src1 ^ src2; }
    if(ex->__PVT__Execute__DOT__inst_logic_unit->op_xori) { op_num++; inst_name = "xori"; res = src1 ^ imm; }
    if(ex->__PVT__Execute__DOT__inst_store_unit->op_sb) { op_num++; inst_name = "sb"; res_en = false; store_en = true; store_addr = src1+imm; store_data = src2; store_bytes = 1; }
    if(ex->__PVT__Execute__DOT__inst_store_unit->op_sh) { op_num++; inst_name = "sh"; res_en = false; store_en = true; store_addr = src1+imm; store_data = src2; store_bytes = 2; }
    if(ex->__PVT__Execute__DOT__inst_store_unit->op_sw) { op_num++; inst_name = "sw"; res_en = false; store_en = true; store_addr = src1+imm; store_data = src2; store_bytes = 4; }
    if(ex->__PVT__Execute__DOT__inst_store_unit->op_sd) { op_num++; inst_name = "sd"; res_en = false; store_en = true; store_addr = src1+imm; store_data = src2; store_bytes = 8; }
    // if(ex->rootp->Execute__DOT__op_fence&&fence_valid) { op_num++; inst_name = "fence"; res_en = false; }
    // if(ex->rootp->Execute__DOT__op_ebreak&&ebreak_valid) { op_num++; inst_name = "ebreak"; res_en = false; }
    // if(ex->rootp->Execute__DOT__op_ecall&&ecall_valid) { op_num++; inst_name = "ecall"; res_en = false; }
    next_pc &= ~1ull;
    fence_valid = false;
    ebreak_valid = false;
    ecall_valid = false;
    if(resw) res = SEXT(res,32);
    if(op_num > 1) {
        printf("mutiple inst op error\n");
        printf("last inst : %s\n",inst_name);
    }
    bool ret_val = true;
    if(pc_en && branch != ex->target_pc_valid_o) {
        ret_val = false;
        printf("branch judge false\n");
        printf("\t\tbranch = %d\n",branch?1:0);
        printf("\t\teq  = %d\n",ex->__PVT__Execute__DOT__inst_branch_unit->eq);
        printf("\t\tne  = %d\n",ex->__PVT__Execute__DOT__inst_branch_unit->ne);
        printf("\t\tge  = %d\n",ex->__PVT__Execute__DOT__inst_branch_unit->ge);
        printf("\t\tgeu = %d\n",ex->__PVT__Execute__DOT__inst_branch_unit->geu);
        printf("\t\tlt  = %d\n",ex->__PVT__Execute__DOT__inst_branch_unit->lt);
        printf("\t\tltu = %d\n",ex->__PVT__Execute__DOT__inst_branch_unit->ltu);
        printf("\t\tpc_valid_o = %d\n",ex->target_pc_valid_o);
    }
    if(pc_en && next_pc != ex->target_pc_o) {
        if(branch) {
            ret_val = false;
            printf("branch dest addr false\n");
        }
    }
    if(res_en && res != dest_data) {
        ret_val = false;
        printf("dest data false\n");
    }
    if(load_en && load_addr != ex->load_addr) {
        ret_val = false;
        printf("load addr false\n");
    }
    if(load_en && ex->load_valid == 0) {
        ret_val = false;
        printf("load valid false\n");
    }
    if(load_en && load_sign != ex->load_sign) {
        ret_val = false;
        printf("load sign false\n");
    }
    if(load_en && ex->load_valid == 0) {
        ret_val = false;
        printf("load sign false\n");
    }
    if(store_en) {
        if(ex->store_valid == 0) {
            ret_val = false;
            printf("store valid false\n");
        }
        if(store_addr != ex->store_addr) {
            ret_val = false;
            printf("store addr false\n");
        }
        if(store_data != ex->store_data) {
            ret_val = false;
            printf("store data false\n");
        }
        if(store_bytes != ex->store_bytes) {
            ret_val = false;
            printf("store bytes false\n");
        }
    }
    return ret_val;


}
void print_info(VExecute *ex){
    int64_t src1 = ex->src1;
    int64_t src2 = ex->src2;
    int64_t imm = ex->imm;
    int64_t res = ex->dest_data;
    uint64_t pc_i = ex->pc_i;
    uint64_t pc_o = ex->pc_o;
    int32_t src1_w = SEXT(ex->src1,32);
    int32_t src2_w = SEXT(ex->src2,32);
    int32_t imm_w = SEXT(ex->imm,32);
    int32_t res_w = SEXT(ex->dest_data,32);
    printf("\t\tsrc1:%" PRId64 " \tsrc2:%" PRId64 " \timm:%" PRId64 " \tpc_i:0x%lx" " \tpc_o:0x%lx" " \tres:%" PRId64 "\n",src1,src2,imm,pc_i,pc_o,res);
    printf("\t\tsrc1_w:%" PRId32 " \tsrc2_w:%" PRId32 " \timm_w:%" PRId32 " \tres_w:%" PRId32 "\n",src1_w,src2_w,imm_w,res_w);
    printf("\t\tmid_val1:%" PRId64 " \tmid_val2:%" PRId64 "\n",(int64_t)ex->__PVT__Execute__DOT__inst_arithmetic_unit->mid_val_1,(int64_t)ex->__PVT__Execute__DOT__inst_arithmetic_unit->mid_val_2);
    printf("\t\tarithmetic op_val1:%" PRId64 " \top_val2:%" PRId64 "\n",(int64_t)ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_val_1,(int64_t)ex->__PVT__Execute__DOT__inst_arithmetic_unit->op_val_2);
    printf("\t\tlogic op_val1:%" PRId64 " \top_val2:%" PRId64 " \tshift_amount:%d" "\n" ,ex->__PVT__Execute__DOT__inst_logic_unit->op_value_1,ex->__PVT__Execute__DOT__inst_logic_unit->op_value_2,ex->__PVT__Execute__DOT__inst_logic_unit->shift_amount);
}

int main(int argc, char **argv, char **env)
{
    Verilated::commandArgs(argc, argv);
    // Verilated::traceEverOn(true); //打开追踪功能
    // VerilatedVcdC* tfp = new VerilatedVcdC; //初始化VCD对象指针
    VExecute *top = new VExecute;
    // top->trace(tfp, 0);   
    // tfp->open("wave_execute.vcd"); //打开vcd
    srand(0);
#define test_cnt 1000000
    printf("inst execute test begin\n");
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
    uint64_t start_us = now.tv_sec * 1000000 + now.tv_nsec / 1000;
    for (int i = 0; i < test_cnt; i++)
    {
        rand_op(top,(enum ALU)rand_n(false,OTHER_UNIT));
        uint8_t rd = rand_n(false,1<<5);
        int64_t src1 = rand();
        int64_t src2 = rand();
        int64_t imm = rand();
        top->pc_i = CONFIG_MBASE + i*4;
        top->dest_i = imm;
        top->src1 = src1;
        top->src2 = src2;
        top->imm = imm;
        top->eval();
        // tfp->dump(sim_time++); //dump wave
        if(judgeOut(top) == false) {
            if(strcmp("null",inst_name) != 0) {
                printf("inst:%s error\n",inst_name);
                print_info(top);
            } else {
                printf("inst is not support\n");
            }
        } else {
            // printf("inst:%s pass\n",inst_name);
        }
    }

    top->final();
    // tfp->close();


    clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
    uint64_t end_us = now.tv_sec * 1000000 + now.tv_nsec / 1000;
    printf("test cost time : %ldms\n",(end_us-start_us)/1000);
    printf("test insts is %d\n",test_cnt);
    printf("test speed is %d inst/ms\n",(int)(test_cnt/((end_us-start_us)/1000)));
    printf("inst execute test end\n");

    delete top;
    // delete tfp;
    return 0;
}


#ifdef __cplusplus
extern "C" {
#endif
    // DPI IMPORTS
    long long mem_read(long long raddr, svBit is_icache) { return 0; }
    void mem_write(long long waddr, long long wdata, char wmask) { return; }
    // void other_inst(svBit ecall, svBit fence, svBit ebreak) { return; }
    long long pmem_read(long long raddr, char rsize, char is_icache) { return 0; }
    void pmem_write(long long waddr, long long wdata, char wmask) { return; }
    void raise_hard_intr() {}
    void submit_inst(long long pc, int inst, long long next_pc) {}
    void difftest_memory_access(long long addr) {}
#ifdef __cplusplus
}
#endif
